<template>
  <ToolbarButton
    icon="i-ri-file-word-line"
    title="导出Word"
    :large-icon="props.largeIcon"
    :hide-text="props.hideText"
    @click="exportDocx"
  />
  <a ref="downloadRef" class="hidden"></a>
</template>

<script setup lang="ts">
import { inject, ref, onUnmounted, type Ref } from "vue";
import type { Editor } from "@tiptap/vue-3";
import ToolbarButton from "../../../button/index.vue";
import {
  Document,
  Packer,
  Paragraph,
  TextRun,
  HeadingLevel,
  Table,
  TableRow,
  TableCell,
  UnderlineType,
  ShadingType,
} from "docx";

defineOptions({ name: "ExportWord" });

const props = withDefaults(
  defineProps<{
    largeIcon?: boolean;
    hideText?: boolean;
  }>(),
  {
    largeIcon: false,
    hideText: true,
  }
);

const editor = inject<Ref<Editor | null>>("editor");
const downloadRef = ref<HTMLAnchorElement | null>(null);

// 导出过程中的定时器和资源引用
let exportTimers: {
  clickTimer: ReturnType<typeof setTimeout> | null;
  revokeTimer: ReturnType<typeof setTimeout> | null;
  url: string | null;
  element: HTMLAnchorElement | null;
} = {
  clickTimer: null,
  revokeTimer: null,
  url: null,
  element: null,
};

function htmlToDocxBlocks(html: string): (Paragraph | Table)[] {
  const parser = new DOMParser();
  const doc = parser.parseFromString(html, "text/html");
  const blocks: Array<Paragraph | Table> = [];

  const normalizeColor = (
    color: string | null | undefined
  ): string | undefined => {
    if (!color) return undefined;
    const c = color.trim();
    if (/^#?[0-9a-fA-F]{6}$/.test(c)) return c.replace("#", "").toUpperCase();
    // rgb(r,g,b)
    const m = c.match(
      /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/
    );
    if (m) {
      const r = Number(m[1]).toString(16).padStart(2, "0");
      const g = Number(m[2]).toString(16).padStart(2, "0");
      const b = Number(m[3]).toString(16).padStart(2, "0");
      return `${r}${g}${b}`.toUpperCase();
    }
    return undefined;
  };

  const normalizeFontSizeHalfPoints = (
    size: string | null | undefined
  ): number | undefined => {
    if (!size) return undefined;
    const s = size.trim().toLowerCase();
    // 12pt -> 24 (half-points)
    const pt = s.match(/^(\d+(?:\.\d+)?)pt$/);
    if (pt) return Math.round(parseFloat(pt[1]) * 2);
    // 16px -> 12pt -> 24 half-points (1px ≈ 0.75pt)
    const px = s.match(/^(\d+(?:\.\d+)?)px$/);
    if (px) return Math.round(parseFloat(px[1]) * 0.75 * 2);
    return undefined;
  };

  const pickFontFamily = (
    family: string | null | undefined
  ): string | undefined => {
    if (!family) return undefined;
    // 取第一个字体名，并去掉引号
    const first = family
      .split(",")[0]
      ?.trim()
      .replace(/^['"]|['"]$/g, "");
    return first || undefined;
  };

  type RunStyle = {
    bold?: boolean;
    italics?: boolean;
    underline?: boolean;
    strike?: boolean;
    color?: string;
    shadingFill?: string;
    size?: number;
    font?: string;
    superScript?: boolean;
    subScript?: boolean;
  };

  const createRunsFromNode = (
    node: Node,
    inherited: RunStyle = {}
  ): TextRun[] => {
    if (node.nodeType === Node.TEXT_NODE) {
      const raw = node.nodeValue || "";
      const text = raw.replace(/\u00A0/g, " ");
      if (!text) return [];
      return [
        new TextRun({
          text,
          bold: inherited.bold,
          italics: inherited.italics,
          underline: inherited.underline
            ? { type: UnderlineType.SINGLE }
            : undefined,
          strike: inherited.strike,
          color: inherited.color,
          shading: inherited.shadingFill
            ? {
                type: ShadingType.CLEAR,
                color: "auto",
                fill: inherited.shadingFill,
              }
            : undefined,
          size: inherited.size,
          font: inherited.font,
          superScript: inherited.superScript,
          subScript: inherited.subScript,
        }),
      ];
    }
    if (node.nodeType !== Node.ELEMENT_NODE) return [];

    const el = node as HTMLElement;
    const tag = el.tagName.toLowerCase();

    const computedBold =
      tag === "b" ||
      tag === "strong" ||
      el.style.fontWeight === "bold" ||
      parseInt(el.style.fontWeight || "0") >= 600;
    const computedItalic =
      tag === "i" || tag === "em" || el.style.fontStyle === "italic";
    const td = el.style.textDecoration || "";
    const computedUnderline = tag === "u" || td.includes("underline");
    const computedStrike =
      tag === "s" || tag === "del" || td.includes("line-through");
    const color = normalizeColor(el.style.color) || inherited.color;
    const bgColor =
      normalizeColor(el.style.backgroundColor) || inherited.shadingFill;
    const fontSize =
      normalizeFontSizeHalfPoints(el.style.fontSize) ?? inherited.size;
    const font = pickFontFamily(el.style.fontFamily) || inherited.font;
    const isSup =
      tag === "sup" ||
      el.style.verticalAlign === "super" ||
      inherited.superScript;
    const isSub =
      tag === "sub" || el.style.verticalAlign === "sub" || inherited.subScript;

    const merged: RunStyle = {
      bold: inherited.bold || computedBold,
      italics: inherited.italics || computedItalic,
      underline: inherited.underline || computedUnderline,
      strike: inherited.strike || computedStrike,
      color,
      shadingFill: bgColor,
      size: fontSize,
      font,
      superScript: isSup,
      subScript: isSub,
    };

    let runs: TextRun[] = [];
    el.childNodes.forEach((child) => {
      runs = runs.concat(createRunsFromNode(child, merged));
    });
    return runs;
  };

  const pushTextPara = (text: string) => {
    // 替换不间断空格，压缩多空格，保留制表符
    const normalized = text
      .replace(/\u00A0/g, " ")
      .replace(/[ \t]+/g, (m) => (m.includes("\t") ? m : " "))
      .trim();
    if (normalized) {
      blocks.push(new Paragraph({ children: [new TextRun(normalized)] }));
    }
  };

  const processElement = (el: Element) => {
    const tag = el.tagName.toLowerCase();

    // 处理分页符：创建一个空的段落，并在其前分页
    // 在 docx 库中，分页符通过 pageBreakBefore 属性实现
    if (
      (tag === "div" && el.getAttribute("data-type") === "page-break") ||
      (tag === "hr" && el.classList.contains("page-break")) ||
      (tag === "p" &&
        (el.classList.contains("page-break") ||
          el.textContent?.trim() === "\f"))
    ) {
      // 创建一个空段落，用于分页
      // 下一个元素如果有，会在其前分页
      blocks.push(
        new Paragraph({
          children: [new TextRun("")],
          pageBreakBefore: true,
        })
      );
      return;
    }

    if (tag === "h1") {
      blocks.push(
        new Paragraph({
          heading: HeadingLevel.TITLE,
          children: [new TextRun(el.textContent || "")],
        })
      );
      return;
    }
    if (tag === "h2") {
      blocks.push(
        new Paragraph({
          heading: HeadingLevel.HEADING_1,
          children: [new TextRun(el.textContent || "")],
        })
      );
      return;
    }
    if (tag === "h3") {
      blocks.push(
        new Paragraph({
          heading: HeadingLevel.HEADING_2,
          children: [new TextRun(el.textContent || "")],
        })
      );
      return;
    }
    if (tag === "h4") {
      blocks.push(
        new Paragraph({
          heading: HeadingLevel.HEADING_3,
          children: [new TextRun(el.textContent || "")],
        })
      );
      return;
    }
    if (tag === "h5") {
      blocks.push(
        new Paragraph({
          heading: HeadingLevel.HEADING_4,
          children: [new TextRun(el.textContent || "")],
        })
      );
      return;
    }
    if (tag === "h6") {
      blocks.push(
        new Paragraph({
          heading: HeadingLevel.HEADING_5,
          children: [new TextRun(el.textContent || "")],
        })
      );
      return;
    }
    if (tag === "p" || tag === "blockquote") {
      const runs = createRunsFromNode(el);
      if (runs.length > 0) {
        blocks.push(new Paragraph({ children: runs }));
      } else {
        pushTextPara(el.textContent || "");
      }
      return;
    }
    if (tag === "pre" || tag === "code") {
      const text = (el.textContent || "").replace(/\r\n/g, "\n");
      const lines = text.split("\n");
      lines.forEach((line) => pushTextPara(line));
      return;
    }
    if (tag === "ul" || tag === "ol") {
      Array.from(el.children).forEach((li) => {
        if (li.tagName.toLowerCase() === "li") {
          const runs = createRunsFromNode(li);
          if (runs.length > 0) {
            blocks.push(
              new Paragraph({ children: [new TextRun("• "), ...runs] })
            );
          } else {
            pushTextPara(`• ${(li.textContent || "").trim()}`);
          }
        }
      });
      return;
    }
    if (tag === "table") {
      const rowNodes = Array.from(
        el.querySelectorAll(":scope > tbody > tr, :scope > tr")
      );
      const rows = rowNodes.map((tr) => {
        const cellNodes = Array.from(tr.children).filter((c) => {
          const n = c.tagName.toLowerCase();
          return n === "td" || n === "th";
        }) as Element[];
        const cells = cellNodes.map((td) => {
          const cellRuns = createRunsFromNode(td);
          const paragraph = new Paragraph({
            children:
              cellRuns.length > 0
                ? cellRuns
                : [
                    new TextRun(
                      (td.textContent || "").replace(/\u00A0/g, " ").trim()
                    ),
                  ],
          });
          return new TableCell({ children: [paragraph] });
        });
        return new TableRow({ children: cells });
      });
      if (rows.length > 0) {
        blocks.push(new Table({ rows }));
      }
      return;
    }
    if (tag === "br") {
      blocks.push(new Paragraph({ children: [new TextRun("")] }));
      return;
    }
    if (tag === "img") {
      /* 暂不处理，后续增强 */ return;
    }
    // 对于 div/section 等容器：递归其子元素，避免整块合并
    Array.from(el.children).forEach((child) => processElement(child));
  };

  // 仅遍历 body 的一级块，避免重复与合并
  Array.from(doc.body.children).forEach((el) => processElement(el));

  if (blocks.length === 0) {
    pushTextPara(doc.body.textContent || "");
  }
  return blocks;
}

const exportDocx = async () => {
  if (!editor?.value) return;
  const html = editor.value.getHTML() || "";
  const children = htmlToDocxBlocks(html);

  const docxDocument = new Document({
    sections: [
      {
        properties: {},
        children,
      },
    ],
  });

  const blob = await Packer.toBlob(docxDocument);

  // 使用原生下载，延迟 revoke，避免部分浏览器读取不到内容
  const url = URL.createObjectURL(blob);
  const a = window.document.createElement("a");
  a.href = url;
  a.download = `文档_${new Date().toISOString().slice(0, 10)}.docx`;
  a.style.display = "none";
  window.document.body.appendChild(a);
  
  // 保存引用以便清理
  exportTimers.url = url;
  exportTimers.element = a;
  
  exportTimers.clickTimer = setTimeout(() => {
    a.click();
    exportTimers.clickTimer = null;
    
    exportTimers.revokeTimer = setTimeout(() => {
      URL.revokeObjectURL(url);
      if (a.parentNode) {
        a.remove();
      }
      exportTimers.revokeTimer = null;
      exportTimers.url = null;
      exportTimers.element = null;
    }, 1500);
  }, 0);
};

// 组件卸载时清理定时器和资源
onUnmounted(() => {
  // 清理定时器
  if (exportTimers.clickTimer) {
    clearTimeout(exportTimers.clickTimer);
    exportTimers.clickTimer = null;
  }
  if (exportTimers.revokeTimer) {
    clearTimeout(exportTimers.revokeTimer);
    exportTimers.revokeTimer = null;
  }
  
  // 清理 URL 和 DOM 元素
  if (exportTimers.url) {
    try {
      URL.revokeObjectURL(exportTimers.url);
    } catch (e) {
      // 忽略错误
    }
    exportTimers.url = null;
  }
  
  if (exportTimers.element && exportTimers.element.parentNode) {
    exportTimers.element.remove();
    exportTimers.element = null;
  }
});
</script>

<style scoped>
.hidden {
  display: none;
}
</style>
